﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GameLibrary.Map
{
    using Tile;
    using Unit;
    using Settlement;
    using Interfaces;
    using Events;
    /// <summary>
    /// Game map.
    /// singleton.
    /// </summary>
    [Serializable]
    public class GameMap
    {
        #region variables

        public TileMap Map { get; private set; }

        /// <summary>
        /// How many region in width
        /// </summary>
        public int RegionWidth { get; private set; }
        /// <summary>
        /// How many region in height
        /// </summary>
        public int RegionHeight { get; private set; }

        /// <summary>
        /// Store region map which segment map into small regions.
        /// Each also store cities and units in that region.
        /// Aim: help city building, recon, and battlefield planning.
        /// </summary>
        private Region<Settlement, Unit>[,] RegionMap;

        /// <summary>
        /// Store all cities.
        /// </summary>
        private MapObjects Settlements = new MapObjects();
        /// <summary>
        /// Store all units.
        /// </summary>
        private MapObjects Units = new MapObjects();

        #endregion

        #region constructor

        private GameMap()
        {
            Map = TileMap.UniqueInstance;

            this.RegionWidth = (int) Math.Ceiling(GMHelper.Width / (double) GMHelper.RegionWidth);
            this.RegionHeight = (int ) Math.Ceiling(GMHelper.Height/(double)GMHelper.RegionHeight);
            
            this.RegionMap = new Region<Settlement, Unit>[this.RegionWidth, this.RegionHeight];
            for (int i = 0; i < this.RegionWidth; i++)
                for (int j = 0; j < this.RegionHeight; j++)
                    this.RegionMap[i, j] = new Region<Settlement, Unit>(new Position(i * GMHelper.RegionWidth, j * GMHelper.RegionHeight));
        }

        #endregion

        #region singleton

        /// <summary>
        /// Singleton creator and keeper
        /// </summary>
        private class GameMapGetter
        {
            static GameMapGetter() { }
            internal static GameMap UniqueInstance = new GameMap();
        }

        public static GameMap UniqueInstance
        {
            get
            {
                return GameMapGetter.UniqueInstance;
            }
        }

        #endregion

        #region events
/*
        private void LocationChanged(object sender, LocationChangedEventArgs e)
        {
            this.RegionMap[
                e.NewPosition.X / GMHelper.RegionWidth,
                e.NewPosition.Y / GMHelper.RegionHeight].AddUnit(e.Obj as Unit);
        }

        private void LocationChanging(object sender, LocationChangingEventArgs e)
        {
            this.RegionMap[
                e.Obj.Location.X/ GMHelper.RegionWidth,
                e.Obj.Location.Y / GMHelper.RegionHeight].RemoveUnit(e.Obj.ID);
        }
        private void ConnectToEventDelegate(IMapObjectWithEventHandler obj)
        {
            obj.LocationChanged += this.LocationChanged;
            obj.LocationChanging += this.LocationChanging;
        }

        private void DisconnectEvent(IMapObjectWithEventHandler obj)
        {
            obj.LocationChanged -= this.LocationChanged;
            obj.LocationChanging -= this.LocationChanging;
        }
*/

        #endregion

        #region add

        internal virtual bool AddSettlement(Settlement settlement)
        {
            if (this.Settlements.ContainsID(settlement.ID) == true)
                return false;
            else
            {
            //    this.ConnectToEventDelegate(city);
                this.AddSettlementToRegion(settlement);
                this.Settlements.Add(settlement);
                return true;
            }
        }

        private void AddSettlementToRegion(Settlement settlement)
        {
            Position pos = MapHelper.FindRegion(settlement.Location);
            this.RegionMap[pos.X, pos.Y].AddSettlement(settlement);
        }

        internal virtual bool AddUnit(Unit unit)
        {
            if (this.Units.ContainsID(unit.ID) == true)
                return false;
            else
            {
                this.AddUnitToRegion(unit);
                this.Units.Add(unit);
                return true;
            }
        }

        private void AddUnitToRegion(Unit unit)
        {
            Position pos = this.ChangeToArrayIndex(unit.Location);
            this.RegionMap[pos.X, pos.Y].AddUnit(unit);
        }

        #endregion

        #region Remove

        internal bool Remove(uint id)
        {
            return this.RemoveSettlement(id)
                || this.RemoveUnit(id);
        }

        internal bool RemoveSettlement(uint cityID)
        {
            if (this.Settlements.ContainsID(cityID))
                return this.RemoveSettlement(this.Settlements[cityID] as Settlement);
            else
                return false;
        }

        internal bool RemoveSettlement(Settlement Settlement)
        {
            if (this.Settlements.ContainsID(Settlement.ID) == false)
                return false;
            else
            {
             //   this.DisconnectEvent(Settlement);
                RemoveSettlementFromRegion(Settlement);
                this.Settlements.Remove(Settlement.ID);
                return true;
            }            
        }

        internal bool RemoveUnit(uint unitID)
        {
            if (this.Units.ContainsID(unitID))
                return this.RemoveUnit(this.Units[unitID] as Unit);
            else
                return false;
        }

        internal bool RemoveUnit(Unit unit)
        {
            if (this.Units.ContainsID(unit.ID) == false)
                return false;
            else
            {
             //   this.DisconnectEvent(unit);
                this.RemoveUnitFromRegion(unit);
                this.Units.Remove(unit.ID);
                return true;
            }            
        }

        private bool RemoveSettlementFromRegion(Settlement settlement)
        {
            Position pos = MapHelper.FindRegion(settlement.Location);
            return this.RegionMap[pos.X, pos.Y].RemoveSettlement(settlement.ID);
        }

        private bool RemoveUnitFromRegion(Unit unit)
        {
            Position pos = MapHelper.FindRegion(unit.Location);
            return this.RegionMap[pos.X, pos.Y].RemoveUnit(unit.ID);
        }

        #endregion

        #region get

        /// <summary>
        /// Return regioninfo of specified region position.
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        internal RegionInfo GetRegionInfo(Position pos)
        {
            pos = this.ChangeToArrayIndex(pos);
            return new RegionInfo(this.RegionMap[pos.X, pos.Y]);
        }

        public double GetMoveCost(Position pos)
        {
            double total = this.GetTileType(pos).BaseMoveCost;
            uint roadLevel = 1;
            var settlement = this.GetSettlementByPosition(pos);
            if (settlement != null)
                roadLevel += settlement.GetBuildingLevel(SettlementHelper.Road);
            return total / roadLevel;
        }

        public TileType GetTileType(Position pos)
        {
            var tile = this.Map[pos.X, pos.Y];
            return tile;
        }

        /// <summary>
        /// Return null if not exist
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public Settlement GetSettlementByID(uint id)
        {
            var obj = this.Settlements[id];
            if (obj == null)
                return null;
            return (Settlement)obj;
        }

        /// <summary>
        /// Return null if not exist.
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        public Settlement GetSettlementByPosition(Position pos)
        {
            var list = this.Settlements.GetObjsByPosition(pos);
            if (list == null
                || list.Count <=0)
                return null;
            else
                return (Settlement)list[0];
        }

        /// <summary>
        /// return null if not exist
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public Unit GetUnitByID(uint id)
        {
            var unit = this.Units[id];
            if (unit == null)
                return null;

            return (Unit)unit;
        }

        /// <summary>
        /// Return null if nothing exist on position
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        public List<Unit> GetUnitsByPosition(Position pos)
        {
            var tmp = this.Units.GetObjsByPosition(pos);
            if(tmp == null)
                return null;
            List<Unit> list = new List<Unit>();
            foreach (var u in tmp)
                list.Add((Unit)u);
            return list;
        }

        public IEnumerable<Settlement> GetSettlements()
        {
            foreach (var settlement in this.Settlements.Values)
                yield return (Settlement)settlement;
        }

        /// <summary>
        /// Get all cities in a region which have region position pos
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        public IEnumerable<Settlement> GetRegionSettlements(Position pos)
        {
            pos = this.ChangeToArrayIndex(pos);
            return this.RegionMap[pos.X, pos.Y].GetSettlements();
        }

        /// <summary>
        /// Get all units in a region which have region position pos
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        public IEnumerable<Unit> GetRegionUnits(Position pos)
        {
            pos = this.ChangeToArrayIndex(pos);
            return this.RegionMap[pos.X, pos.Y].GetUnits();
        }

        #endregion

        #region contains

        public bool ContainsSettlementID(uint id)
        {
            return this.Settlements.ContainsID(id);
        }

        public bool ContainsUnitID(uint id)
        {
            return this.Units.ContainsID(id);
        }

        public bool RegionContainsSettlementID(uint id, Position pos)
        {
            return this.RegionMap[pos.X, pos.Y].ContainsSettlementID(id);
        }

        public bool RegionContainsUnitID(uint id, Position pos)
        {
            return this.RegionMap[pos.X, pos.Y].ContainsUnitID(id);
        }    
        
        #endregion

        #region utility

        private Position ChangeToArrayIndex(Position pos)
        {
            return new Position(
                pos.X / GMHelper.RegionWidth,
                pos.Y / GMHelper.RegionHeight);
        }

        #endregion
    }
}